
/* tcb.c
 * Transactional circular buffer.
 *
 * Copyright (C) 2013 Michael Krivoruchko
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "tcb.h"

/********************************************************************
 * @brief  Allocates an element for next buffer write operation
 * @param  uint32_t Current buffer state field
 * @param  uint32_t Total number of elements in the buffer
 * @return uint32_t State of the buffer or 0
 */
uint32_t tcb__alloc(volatile uint32_t * state, uint32_t elm_num)
{
	register uint32_t st;

	do {
		st = __LDREXW(state);
		if((tcb__rcnt(st) < elm_num) && (tcb__wcnt(st) < TCB_MAX_PREALLOC)) {
			st = tcb__wext(st, elm_num);
		} else {
			st = 0;
			__CLREX();
			break;
		}
	} while(__STREXW(st, state));

	return st;
}

/********************************************************************
 * @brief  Completes current buffer write operation
 * @param  uint32   Current buffer state field
 */
void tcb__commit(volatile uint32_t * state)
{
	register uint32_t st;
	do {
		st = __LDREXW(state);
	} while(__STREXW(tcb__wclr(st), state));
}

/********************************************************************
 * @brief  Remove element from the buffer's tail
 * @param  uint32_t * Buffer state field
 * @param  uint32_t   Total number of elements in the buffer
 * @return uint32_t   Index of a new element in the buffer's tail or UINT32_MAX
 */
uint32_t tcb__next(uint32_t * state, uint32_t elm_num)
{
	register uint32_t ridx = UINT32_MAX;
	register uint32_t st   = tcb__rdec(*state);

	if(tcb__rcnt(*state) > tcb__wcnt(*state)) {
		*state = st;
		ridx = (tcb__rcnt(st) <= tcb__widx(st))?
				(tcb__widx(st) - tcb__rcnt(st)):
				(elm_num + tcb__widx(st) - tcb__rcnt(st));
	}

	return ridx;
}

/********************************************************************
 * @brief  Provides access to an element of the buffer by index
 * @param  uint32_t Buffer state field
 * @param  uint32_t Total number of elements in the buffer
 * @param  uint32_t Index of an element where zero is the tail of the buffer
 * @return uint32_t Index of an element at specified position from the buffer's
 *                  tail or UINT32_MAX
 */
uint32_t tcb__get(uint32_t state, uint32_t elm_num, uint32_t idx)
{
	register uint32_t ridx = UINT32_MAX;

	if(tcb__rcnt(state) > tcb__wcnt(state)) {
		register uint32_t off = tcb__rcnt(state) - 1;
		if(idx <= off) {
			off -= idx;
			ridx = (off <= tcb__widx(state))?
				(tcb__widx(state) - off): (elm_num + tcb__widx(state) - off);
		}
	}

	return ridx;
}
